Step 33: ApiError

We should be doing input validation and error handling in Bookmark DAO as well as in the bookmarks route handler functions. To that aim, we will create a new class. Add ApiErro.js to the src/model folder:

class ApiError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

export default ApiError;

The status attribute will store the HTTP status number.

In APIs that conform to HTTP, the error status is a "code" (number) returned to the client that signals the success/failure of their request. Here are the common status codes and their meaning:

StatusMeaning
200 (OK)This is the standard response for successful HTTP requests.
201 (CREATED)This is the standard response for an HTTP request that resulted in an item being successfully created.
204 (NO CONTENT)This is the standard response for successful HTTP requests, where nothing is being returned in the response body.
400 (BAD REQUEST)The request cannot be processed because of bad request syntax, excessive size, or another client error.
403 (FORBIDDEN)The client does not have permission to access this resource.
404 (NOT FOUND)The resource could not be found at this time. It is possible it was deleted, or does not exist yet.
500 (INTERNAL SERVER ERROR)The generic answer for an unexpected failure if there is no more specific information available.

Let’s update the create method in the src/data/BookmarkDAO.js file:

// return the created bookmark
// throws ApiError when title or url are invalid
async create({ title, url }) {
  try {
    const bookmark = await Bookmark.create({title, url});
    return bookmark;
  } catch(err) {
    throw new ApiError(400, err.message);
  }
}

Add these tests to tests/data/BookmarkDAO.test.js file:

describe("test create() throws error", () => {
    it("empty title", async () => {
      try {
        const title = "";
        const url = faker.internet.url();
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });

    it("null title", async () => {
      try {
        const title = null;
        const url = faker.internet.url();
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });

    it("undefined title", async () => {
      try {
        const title = undefined;
        const url = faker.internet.url();
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });

    it("empty url", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = "";
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });

    it("null url", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = null;
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });

    it("undefined url", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = undefined;
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });

    it("invalid url", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = faker.lorem.sentence();
        await bookmarkDAO.create({ title, url });
      } catch (err) {
        expect(err.status).toBe(400);
      }
    });
  });

Run the tests and make sure they all pass. Then, save and commit the changes.